<template>
    <section class="wrap">
      <h2>Vue3 图片批量压缩 + 打包下载</h2>
  
      <!-- 上传区域 -->
      <div class="upload-area" :class="{ active: isOver }" @click="fileEl.click()" @drop.prevent="handleDrop"
        @dragover.prevent="isOver = true" @dragleave="isOver = false">
        <input ref="fileEl" type="file" multiple accept="image/*" style="display: none"
          @change="e => handleFiles(e.target.files)" />
        <p>点击或拖拽图片到此处</p>
      </div>
  
      <!-- 任务列表 -->
      <ul v-if="tasks.length" class="list">
        <li v-for="t in tasks" :key="t.id">
          <img :src="t.preview" />
          <div class="info">
            <span>{{ t.name }}</span>
            <small>
              原：{{ sizeFmt(t.originSize) }} → 压：{{ sizeFmt(t.compressedSize) }}
              ({{ t.ratio }})
            </small>
          </div>
          <a :href="t.url" :download="t.name">单张下载</a>
        </li>
      </ul>
  
      <!-- 底部按钮 -->
      <div v-if="tasks.length" class="foot">
        <button class="btn btn-primary" @click="downloadZip">打包下载全部</button>
        <button class="btn btn-danger" @click="tasks = []">清空</button>
      </div>
    </section>
  </template>
  
  <script setup>
  /* ---------------- 依赖 ---------------- */
  import { ref, nextTick } from 'vue'  // 图片压缩
  import Compressor from 'compressorjs'      // npm i compressorjs 
  import JSZip from 'jszip'                  // npm i jszip  JSZip库文件压缩与解压
  import { saveAs } from 'file-saver'        // npm i file-saver  使用file-saver导出文件，下载Excel文件、下载图片、下载文本
  
  /* ---------------- 状态 ---------------- */
  const fileEl = ref()
  const isOver = ref(false)
  const tasks = ref([])   // {id,name,preview,url,originSize,compressedSize,ratio}
  
  /* ---------------- 方法 ---------------- */
  function sizeFmt(b) {
    return b < 1024 * 1024
      ? (b / 1024).toFixed(1) + ' KB'
      : (b / 1024 / 1024).toFixed(2) + ' MB'
  }
  
  // 统一入口：fileList -> 逐个压缩
  async function handleFiles(fileList) {
    isOver.value = false
      ;[...fileList].forEach(f => compressFile(f))
  }
  
  function handleDrop(e) {
    isOver.value = false
    handleFiles(e.dataTransfer.files)
  }
  
  // 单张压缩
  function compressFile(rawFile) {
    const id = Date.now() + Math.random()
    new Compressor(rawFile, {
      quality: 0.7,          // 可调
      maxWidth: 1600,        // 最大宽
      mimeType: 'image/jpeg',
      success(blob) {
        const url = URL.createObjectURL(blob)
        tasks.value.push({
          id,
          name: rawFile.name.replace(/\.\w+$/, '.jpg'), // 统一后缀
          preview: url,
          url,
          originSize: rawFile.size,
          compressedSize: blob.size,
          ratio: `-${((1 - blob.size / rawFile.size) * 100).toFixed(0)}%`
        })
      },
      error(err) {
        alert('压缩失败：' + err.message)
      }
    })
  }
  
  // 打包下载
  async function downloadZip() {
    const zip = new JSZip()
    await Promise.all(
      tasks.value.map(async t => {
        const r = await fetch(t.url)
        zip.file(t.name, await r.blob())
      })
    )
    const content = await zip.generateAsync({ type: 'blob' })
    saveAs(content, `compressed-images-${Date.now()}.zip`)
  }
  </script>
  
  <style scoped>
  .wrap {
    max-width: 1100px;
    margin: 24px auto;
  }
  
  .upload-area {
    border: 2px dashed var(--color-border);
    background: var(--color-surface);
    border-radius: var(--radius);
    padding: 40px 0;
    text-align: center;
    cursor: pointer;
    transition: .2s;
    box-shadow: var(--shadow);
  }
  
  .upload-area.active {
    border-color: var(--color-primary);
    background: var(--color-hover);
  }
  
  .list {
    list-style: none;
    padding: 0;
    margin-top: 20px;
  }
  
  .list li {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 8px 0;
    border-bottom: 1px solid var(--color-border);
  }
  
  .list img {
    width: 60px;
    height: 60px;
    object-fit: cover;
    border-radius: 4px;
  }
  
  .info {
    flex: 1;
    display: flex;
    flex-direction: column;
    font-size: 14px;
  }
  
  .foot {
    margin-top: 20px;
    display: flex;
    gap: 12px;
  }

  button { cursor: pointer; }
  </style>